package com.autonavi.yunda.yunji.common.utils.groovy;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import groovy.lang.Binding;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
import groovy.lang.GroovyShell;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.io.IOUtils;
import org.codehaus.groovy.ast.stmt.SynchronizedStatement;
import org.codehaus.groovy.classgen.BytecodeExpression;
import org.codehaus.groovy.classgen.BytecodeSequence;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
import org.springframework.util.DigestUtils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

public class GroovyUtils {
    private final static ClassLoader classLoader = GroovyUtils.class.getClassLoader();
    private final static CompilerConfiguration config = new CompilerConfiguration();
    public final static GroovyClassLoader groovyClassLoader;

    static {
        // 创建SecureASTCustomizer
        SecureASTCustomizer secureCustomizer = new SecureASTCustomizer();
        /* disable calling the System.exit() method and use of other dangerous imports */
        List<String> blackClassNames = Arrays.asList(
                System.class.getName(),
                GroovyShell.class.getName(),
                GroovyClassLoader.class.getName(),
                Runtime.class.getName(),
                Files.class.getName(),
                File.class.getName(),
                Socket.class.getName());
        secureCustomizer.setDisallowedImports(blackClassNames);
        secureCustomizer.setDisallowedStarImports(blackClassNames);
        secureCustomizer.setDisallowedStaticImports(blackClassNames);
        secureCustomizer.setDisallowedStaticStarImports(blackClassNames);
        secureCustomizer.setDisallowedReceivers(blackClassNames);
        secureCustomizer.setIndirectImportCheckEnabled(true);
        /* disable dangerous Expressions */
        secureCustomizer.setDisallowedExpressions(List.of(BytecodeExpression.class));
        secureCustomizer.setDisallowedStatements(Arrays.asList(BytecodeSequence.class, SynchronizedStatement.class));
        config.addCompilationCustomizers(secureCustomizer);
        groovyClassLoader = new GroovyClassLoader(classLoader, config);
    }


    /**
     * groovy脚本经编译后的class缓存，最后一次访问的一小时后缓存过期
     */
    private static Cache<String, Class> CLASS_CACHE
            = CacheBuilder.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES).build();

    public static <T> Class<T> loadClassGroovyScriptWithCache(String script) {
        //获取脚本MD5
        String md5Hex = DigestUtils.md5DigestAsHex(script.getBytes(Charset.forName("UTF-8")));
        Class target = CLASS_CACHE.getIfPresent(md5Hex);
        if (null != target) {
            //检查缓存是否已存在对应类，如有则直接返回，降低Metaspace压力
            return target;
        }
        //编译对应脚本并加载类
        Class clazz = groovyClassLoader.parseClass(script);
        groovyClassLoader.clearCache();
        CLASS_CACHE.put(md5Hex, clazz);
        return clazz;
    }


    /**
     * 文本类型加载
     */
    public static <T> Class<T> loadClassGroovyScript(String script) {
        return groovyClassLoader.parseClass(script);
    }

    public static <T> Class<T> loadClass(GroovyCodeSource codeSource) {
        return groovyClassLoader.parseClass(codeSource);
    }

    /**
     * 文件类型加载
     */
    public static <T> Class<T> loadClassGroovyFile(String path) throws IOException {
        String script = IOUtils.toString(Objects.requireNonNull(ClassLoader.getSystemResourceAsStream(path)), StandardCharsets.UTF_8);
        return loadClassGroovyScript(script);
    }

    public static void setProperties(Object instance, Map<String, Object> args) throws InvocationTargetException, IllegalAccessException {
        BeanUtils.populate(instance, args);
    }

    public static Object shellEvaluate(Binding binding, String script) {
        GroovyShell shell = new GroovyShell(binding);
        Object res = shell.evaluate(script);
        shell.getClassLoader().clearCache();
        return res;
    }

}
